package com.example.lock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.UUID;

/**
 * redis 实现分布式锁
 *
 * @author mingtian
 */
public class RedisLock {

    /**
     * redis 线程池
     */
    private JedisPool jedisPool;

    /**
     * 定义相同的key名称
     */
    private String redisLockKey = "redis_lock";

    public RedisLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * redis实现分布式锁描述说明：
     * 1.redis  以key（redisKey），value（随机不重复数 锁的id）形式进行存储。
     * 2.redis 实现分布式锁有二个超时 时间问题
     *  2.1 在获取锁之前的超时时间--在尝试获取锁的时候，如果在规定的时间内没有获取到锁，直接放弃。
     *  2.2 在获取锁之后的超时时间--在获取锁成功之后，对应的 key 有对应的有效期，对应的 key 在规定时间内失效。
     */


    /**
     * 基于redis实现获取锁与释放锁
     *
     * @param acquireTimeOut 获取锁之前的超时时间 毫秒
     * @param timeOut        获取锁之后的超时时间 毫秒
     * @return
     */
    public String getRedisLock(Long acquireTimeOut, Long timeOut) {
        Jedis conn = null;
        try {
            //1.建立连接
            conn = jedisPool.getResource();
            //2.定义redis 对应的 key 和 value  value作用：释放锁。随机生成value
            String randomValue = UUID.randomUUID().toString();
            //3.定义获取锁之后的超时时间,转化为以秒为单位
            int expireLock = (int) (timeOut / 1000);
            //4.定义在获取锁之前的超时时间
            //5.使用循环机制 如果没有获取到锁，要在规定的 acquireTimeOut 时间 保证重复的获取锁
            Long endTime = System.currentTimeMillis() + acquireTimeOut;
            ////获取锁:使用循环方式重试获取锁
            while (System.currentTimeMillis() < endTime) {

                //6.使用setNx命令插入对应 key，如果返回1则表示创建成功
                if (conn.setnx(redisLockKey, randomValue) == 1) {
                    //设置 key 对应的有效期，防止死锁
                    conn.expire(redisLockKey, expireLock);
                    return randomValue;
                }
                //为什么获取锁之后，还要设置锁的超时时间？       目的是防止死锁现象。
                //Zookeeper实现分布式锁通过什么方式防止死锁现象？ 临时节点，当会话关闭的时候临时节点被删除。设置Session的有效期。
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭连接
            if (conn != null) {
                conn.close();
            }
        }
        return null;

    }


    /**
     * 释放锁
     * 释放锁有2种方式
     * 1：key 设置有效期，自动删除
     * 2：整个程序执行完毕情况下，删除对应的key
     * 如果直接使用 conn.del(redisLockKey);无法保证是自己创建的锁，可能会产生误删除
     * 所以需要保证删除的锁是自己创建的 redisLockKey，根据  randomValue 。
     *
     * @param randomValue 作用：释放属于自己锁
     */
    public void unRedisLock(String randomValue) {

        Jedis conn = null;
        conn = jedisPool.getResource();
        try {
            //删除自己对应的锁
            if (conn.get(redisLockKey).equals(randomValue)) {
                System.out.println("释放锁" + Thread.currentThread().getName() + ",randomValue:" + randomValue);
                //删除
                conn.del(redisLockKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
    }
}
